package com.lx.boot.web.filter;

import com.lx.annotation.Note;
import com.lx.boot.OS;
import com.lx.boot.cache.util.CacheUtils;
import com.lx.boot.web.config.RequestInfo;
import com.lx.constant.DefaultResult;
import com.lx.entity.Result;
import com.lx.entity.UserInfo;
import com.lx.util.LX;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.filters.AddDefaultCharsetFilter.ResponseWrapper;
import org.apache.http.impl.client.RequestWrapper;
import org.springframework.http.MediaType;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.lx.constant.DefaultBaseConstant.AUTHORIZATION;
import static com.lx.constant.DefaultBaseConstant.FIRM_ID;

@Slf4j
public abstract class ApiEncryptFilter {
    public static final List<String> POST_METHOD = Arrays.asList("POST","PUT");

    public void filter(UserInfo userInfo,FilterChain filterChain) throws Exception {
        RequestInfo requestInfo = OS.getRequestInfo();
        HttpServletRequest request = requestInfo.getRequest();
        String ip = requestInfo.getIp();
        String url = requestInfo.getUrl();
        ServletRequest servletRequest;
        if(requestInfo.getWebProperties().isLogHeader()){
            log.info("url==>{},请求头:{}",url, LX.toJSONString(getHeadersInfo(request)));
        }
        if (isMultipart(request.getContentType())){
            servletRequest = request;
            log.info("文件上传,url==>{} [ip:{}] [用户信息:{} ]",url,ip,LX.toJSONString(userInfo));
        }else{
            byte [] requestBody = LX.ifNull(LX.inputStreamToByteArray(request.getInputStream()) , new byte[0]);
            servletRequest = new RequestWrapper(request, requestBody);
            Map<String, String[]> parameterMap = request.getParameterMap();
            String paraStr = LX.toJSONString(parameterMap);
            log.info("url==>{} [ip:{}] [用户信息:{}] [parameter:{}] [body:{} ]",url, ip ,LX.toJSONString(userInfo) ,paraStr.length()>5000?paraStr.substring(0,5000):paraStr ,LX.byteArrayToString(requestBody,5000));
            if (OS.isDevRequest()){ //开发测试调用
                servletRequest = new RequestWrapper(request, requestBody);
            }else{
                servletRequest = getRequestWrapper(request, requestBody,requestInfo.getWebProperties().isNotEncryptionUrl(url));
                if (servletRequest == null){
                    return;
                }
            }
        }
        ResponseWrapper responseWrapper = new ResponseWrapper(requestInfo.getResponse());
        filterChain.doFilter(servletRequest,responseWrapper);
        response(getResponseWrapper(responseWrapper,requestInfo.getWebProperties().isReturnEncryption()));
    }




    public void response(ResponseWrapper responseWrapper) throws IOException {
        byte[] content = responseWrapper.getContent();
        if (content.length > 0) {
            // 有返回值
            if (isApplicationJson(responseWrapper.getContentType())){
                String outBody = new String(content, StandardCharsets.UTF_8);
                if (outBody!= null && outBody.length()>5000){
                    log.info("响应参数:{}" ,outBody.substring(0,5000));
                }else{
                    log.info("响应参数:{}" , outBody);
                }
            }
        }
        //返回信息
        setReturnInfo(content);
    }

    @Note("对接口入参进行解密 如果返回null 则不进行后续调用直接返回")
    protected abstract RequestWrapper getRequestWrapper(HttpServletRequest request, byte[] bodys, boolean notEncryptionUrl) throws Exception;

    @Note("对接口返回值进行加密")
    protected ResponseWrapper getResponseWrapper(ResponseWrapper responseWrapper,boolean returnEncryption) throws Exception {
        return responseWrapper;
    }

    @Note("设置返回值信息")
    private void setReturnInfo(byte[] bytes) {
        HttpServletResponse response = OS.getResponse();
        if (response == null || response.isCommitted()){
            return;
        }
        try {
            ServletOutputStream out = response.getOutputStream();
            if (bytes != null && bytes.length>0){
                out.write(bytes);
            }
            out.flush();
        }catch (Exception e){
            log.error("输出信息异常:",e);
            OS.responseErrorResult(DefaultResult.RESPONSE_ERROR.getResult());
        }

    }


    @Note("是post 或 put请求")
    protected boolean isPostOrPut(HttpServletRequest request) {
        return POST_METHOD.contains(request.getMethod());
    }

    @Note("是json请求")
    protected boolean isApplicationJson(String contentType) {
        return contentType != null && contentType.contains(MediaType.APPLICATION_JSON_VALUE);
    }

    @Note("是上传文件请求")
    protected boolean isMultipart(String contentType) {
        return contentType != null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE);
    }

    private static final long TIMEOUT = 300000;
    @Note("检查接口时间戳和随机字符串是否使用过")
    public static boolean checkApiTimeAndRepeat(String timestamp, String nonce) {
        if (LX.isEmpty(timestamp)||LX.isEmpty(nonce)){
            LX.exMsg("调用接口异常!请检查参数.");
        }
        Long t = Long.parseLong(timestamp);
        boolean forMoreThan = Math.abs(System.currentTimeMillis() - t) > TIMEOUT;
        LX.exMsg(forMoreThan,"调用接口异常, 请检查客户端时间是否正确:"+LX.getTime());
        if (!CacheUtils.allowAccessTo(nonce,1,5, TimeUnit.MINUTES)){
            LX.exMsg("调用接口异常, 请勿重复调用!");
        }
        return true;
    }

    @Note("获取请求头信息")
    public static Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<String, String>();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }



    class RequestWrapper extends HttpServletRequestWrapper {
        private final byte[] body;


        public RequestWrapper(HttpServletRequest request ,byte[] body) throws IOException {
            super(request);
            this.body = body;
        }

        @Override
        public BufferedReader getReader() {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public int getContentLength() {
            return body.length;
        }

        @Override
        public long getContentLengthLong() {
            return body.length;
        }

        @Override
        public ServletInputStream getInputStream() {
            final ByteArrayInputStream bais = new ByteArrayInputStream(body);
            return new ServletInputStream() {
                @Override
                public int read() {
                    return bais.read();
                }

                @Override
                public int available() {
                    return body.length;
                }

                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }
            };
        }
    }

    class ResponseWrapper extends HttpServletResponseWrapper {
        private ByteArrayOutputStream buffer;
        private ServletOutputStream out;
        public ResponseWrapper(HttpServletResponse httpServletResponse) {
            super(httpServletResponse);
            buffer = new ByteArrayOutputStream();
            out = new WrapperOutputStream(buffer);
        }
        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return out;
        }
        @Override
        public void flushBuffer() throws IOException {
            if (out != null) {
                out.flush();
            }
        }
        public byte[] getContent() throws IOException {
            flushBuffer();
            return buffer.toByteArray();
        }
        public void setContent(byte[] bytes) throws IOException {
            buffer = new ByteArrayOutputStream();
            buffer.write(bytes,0,bytes.length);
        }
        class WrapperOutputStream extends ServletOutputStream {
            private ByteArrayOutputStream bos;
            public WrapperOutputStream(ByteArrayOutputStream bos) {
                this.bos = bos;
            }
            @Override
            public void write(int b) throws IOException {
                bos.write(b);
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setWriteListener(WriteListener arg0) { }
        }

    }
}
